-
Notifications
You must be signed in to change notification settings - Fork 679
Add OANDA RestV20 Historical Instrument Reader #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OANDA RestV20 Historical Instrument Reader #257
Conversation
@femtotrader I create a new Pull Request for several reasons:
I will have a look at implementing some of your suggestions below.
|
ae458ad
to
d8f8d32
Compare
"1T":"M1", | ||
"1min":"M1", | ||
"2T":"M2", | ||
"2min":"M2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you define SUPPORTED_OFFSET_ALIASES
You should have Pandas Offset as key not string shortcut as key.
Because it urge you to repeat several times some values.
In [1]: from pandas.tseries.frequencies import to_offset
In [2]: to_offset("1Min")
Out[2]: <Minute>
In [3]: offset = to_offset("1Min")
In [4]: offset
Out[4]: <Minute>
In [5]: type(offset)
Out[5]: pandas.tseries.offsets.Minute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader OANDA REST v20 only accept predefined freqencies. Eg: 1T is supported since M1 is an accepted value for OANDA REST v20's endpoint however 7T -> M7 is not supported since M7 accepted by the endpoint.
Does it still make sense to have pandas.tseries.offsets.XXXX object
instead of hardcoded Panda Offset aliases ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will use this to_offset
in a try/catch
so if freq
can't be convert to Pandas Offset using to_offset
it will be passed to Oanda directly.
On the other side I don't think it's a problem if user pass freq="7T"
which will be convert to <7 * Minutes>
but it won't be in SUPPORTED_OFFSET_ALIASES
dict keys so we can decide here to raise an Exception to inform user that it's no an accepted timeframe.
Should we define keys for "1min", "1MIN", "1Min"...
In [9]: to_offset("1min")
Out[9]: <Minute>
In [10]: to_offset("1Min")
Out[10]: <Minute>
In [11]: to_offset("1MIN")
Out[11]: <Minute>
I don't think so!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader Ok. So i am thinking of accepting a string or DateOffset for freq
param
How can a DateOffset object be converted back to string ? Eg:
In[1]d = to_offset("1min")
In[2] ds = to_offset_string(d)
Out[2] print(ds) # Displays "1min"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure a DateOffset can be convert back to a "normalized" offset string
In [20]: offset = to_offset("7T")
In [21]: offset.
offset.apply offset.delta offset.kwds offset.nanos offset.rollback
offset.apply_index offset.freqstr offset.n offset.normalize offset.rollforward
offset.copy offset.isAnchored offset.name offset.onOffset offset.rule_code
but it could be an interesting feature to have in Pandas
In [18]: offset.rule_code
Out[18]: 'T'
In [20]: offset = to_offset("7T")
In [21]: offset.n
Out[21]: 7
In [22]: offset.rule_code
Out[22]: 'T'
In [23]: offset = to_offset("7Min")
In [24]: offset.n
Out[24]: 7
In [25]: offset.rule_code
Out[25]: 'T'
a quick and dirty approach could be
In [47]: def normalized_offset_string(offset):
...: if offset.n == 1:
...: return offset.rule_code
...: else:
...: return str(offset.n) + offset.rule_code
There is in fact an attribute to get normalized string from DateOffset it's named: In [63]: from pandas.tseries.frequencies import to_offset
In [64]: to_offset("7Min").freqstr
Out[64]: '7T'
In [65]: to_offset("7MIN").freqstr
Out[65]: '7T'
In [66]: to_offset("7min").freqstr
Out[66]: '7T' |
I'm sorry I don't know "all" Pandas ;-) |
@femtotrader That's fine :) I only expect you to know the answers to the questions I ask :P |
@femtotrader My PR is done. |
"5T":"M5", | ||
"5min":"M5", | ||
"15T":"M15", | ||
"15min":"M15", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
freqstr
DateOffset attribute should avoid to repeat these keys
In [67]: to_offset("15min").freqstr
Out[67]: '15T'
In [68]: to_offset("15T").freqstr
Out[68]: '15T'
In [69]: to_offset("15Min").freqstr
Out[69]: '15T'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader Fixed.
current_end_rfc3339 = current_end.strftime(rfc3339) | ||
|
||
if isinstance(freq, DateOffset): | ||
offsetString = self._normalized_offset_string(freq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
offsetString = freq.freqstr
Sorry;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader Fixed.
PS: No worries at all :)
@@ -56,7 +57,8 @@ def get_quote_google(*args, **kwargs): | |||
|
|||
|
|||
def DataReader(name, data_source=None, start=None, end=None, | |||
retry_count=3, pause=0.001, session=None, access_key=None): | |||
retry_count=3, pause=0.001, session=None, | |||
custom=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
access_key
is required for Enigma datareader.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader Fixed.
oa = DataReader("EUR_USD", data_source="oanda_rest_historical_currency", | ||
custom={ "accountType"="practice", | ||
"accountVersion="0" | ||
"apiToken":"Your private API token" }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oa = DataReader("EUR_USD", data_source="oanda_rest_historical_currency",
custom = {
"accountType": "practice",
"accountVersion": "0",
"apiToken": "Your private API token"
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@femtotrader Fixed
You need to run locally
to fix some Flake8 errors |
@femtotrader PR done :) |
"15T": "M15", | ||
"30T": "M30", | ||
"H": "H1", | ||
"1H": "H1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from pandas.tseries.frequencies import to_offset
to_offset("1H").freqstr
Out[20]: 'H'
to_offset("H").freqstr
Out[21]: 'H'
so we need just "H", not "1H"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"15S": "S15", | ||
"30S": "S30", | ||
"T": "M1", | ||
"1T": "M1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1T key should be removed (same as 1H)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"6H": "H6", | ||
"8H": "H8", | ||
"12H": "H12", | ||
"1D": "D", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be
"D": "D",
because
In: to_offset("1D").freqstr
Out: 'D'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"8H": "H8", | ||
"12H": "H12", | ||
"1D": "D", | ||
"1W": "W", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"W": "W",
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"12H": "H12", | ||
"1D": "D", | ||
"1W": "W", | ||
"1M": "M" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"M": "M"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
df.rename(inplace=True, | ||
index=str, | ||
columns={ | ||
"time": "date", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most DataReader index name is Date
"time": "Date",
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
"bid.c": "bid.close", | ||
}) | ||
|
||
df = df.set_index('date') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
df = df.set_index('Date')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A DataFrame with a MultiIndex as columns will be a great feature to have
http://pandas.pydata.org/pandas-docs/stable/advanced.html
Most other DataReader with candlestick data are using; Open
, High
, Low
, Close
column name (not open
, high
, low
, close
)
Here is an example to show how a DataFrame with MultiIndexed columns works
In: periods = 10
In: idx = pd.date_range("2010-01-01", freq="D", periods=10)
In: columns = [np.array(['Mid', 'Mid', 'Mid', 'Mid', 'Ask', 'Ask', 'Ask', 'Ask', 'Bid', 'Bid', 'Bid', 'Bid']),
np.array(['Open', 'High', 'Low', 'Close', 'Open', 'High', 'Low', 'Close', 'Open', 'High', 'Low', 'Close'])]
In: df = pd.DataFrame(np.random.randn(periods, 12), columns=columns, index=idx)
In: df
Out[59]:
Mid Ask \
Open High Low Close Open High
2010-01-01 -0.357908 0.638564 -0.808755 0.029079 0.150620 0.282146
2010-01-02 1.015372 -0.624254 0.353841 1.491935 -0.322171 0.353117
2010-01-03 0.051334 0.516878 -1.769581 0.812352 -0.528826 0.677380
2010-01-04 -1.681174 0.411583 -1.032651 1.924580 0.162397 -0.422782
2010-01-05 0.512275 -0.112695 -0.705641 0.715283 0.446572 -1.428909
2010-01-06 -1.197475 -0.025064 -0.997523 1.252269 0.217094 -1.086155
2010-01-07 -0.935145 -0.446475 -1.220699 -1.135570 0.369933 1.695358
2010-01-08 -0.259295 0.164924 0.371293 -0.136309 2.280143 -1.256588
2010-01-09 -0.392548 1.762788 -1.656464 1.548067 -0.333061 0.226948
2010-01-10 -0.431441 0.674132 -1.381039 0.892813 0.793012 -0.755067
Bid
Low Close Open High Low Close
2010-01-01 -1.313252 -1.179413 1.873345 -0.719676 -2.595192 1.182884
2010-01-02 -0.670126 0.421320 0.318873 -0.756884 1.189023 -0.785939
2010-01-03 -1.027978 0.609855 0.877085 -0.767293 -1.859567 0.662125
2010-01-04 1.049496 -1.062435 0.577158 -0.068621 -0.080246 -0.786441
2010-01-05 -3.193001 -0.540472 2.350964 -1.983940 2.278458 -0.783962
2010-01-06 -0.101567 -0.145842 -1.600600 0.098544 0.133307 0.262338
2010-01-07 -0.263570 -1.012718 -0.579255 1.360744 1.661800 -2.185659
2010-01-08 -2.260959 -1.143676 -0.900669 0.721042 -0.421265 -0.716886
2010-01-09 -1.229502 -0.442861 -0.767872 0.734038 -0.403405 0.046703
2010-01-10 1.522349 0.463781 0.522694 -0.700728 -0.726378 -1.144720
df["Ask"]
Out[60]:
Open High Low Close
2010-01-01 0.150620 0.282146 -1.313252 -1.179413
2010-01-02 -0.322171 0.353117 -0.670126 0.421320
2010-01-03 -0.528826 0.677380 -1.027978 0.609855
2010-01-04 0.162397 -0.422782 1.049496 -1.062435
2010-01-05 0.446572 -1.428909 -3.193001 -0.540472
2010-01-06 0.217094 -1.086155 -0.101567 -0.145842
2010-01-07 0.369933 1.695358 -0.263570 -1.012718
2010-01-08 2.280143 -1.256588 -2.260959 -1.143676
2010-01-09 -0.333061 0.226948 -1.229502 -0.442861
2010-01-10 0.793012 -0.755067 1.522349 0.463781
df["Ask"]["Open"]
Out[61]:
2010-01-01 0.150620
2010-01-02 -0.322171
2010-01-03 -0.528826
2010-01-04 0.162397
2010-01-05 0.446572
2010-01-06 0.217094
2010-01-07 0.369933
2010-01-08 2.280143
2010-01-09 -0.333061
2010-01-10 0.793012
Freq: D, Name: Open, dtype: float64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: Use Open
instead open
, etc ...
I will look at A DataFrame with a MultiIndex later this week.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: Added MultiIndex support. Thanks for the suggestion @femtotrader !
|
||
def get_credential(self): | ||
return {'accountType': "practice", | ||
'apiToken': "8106b9109c7c6b17d45ff215f6da9d42-1446c148ee0f4ac9274eb6c2be0784b7" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can "hide" this in an environment variable if you want (like Enigma)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: Token removed from tests. Now users can specify the token via an env variable
self.access_credential['apiToken'] = "" | ||
|
||
def read(self): | ||
dfs = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using an OrderedDict will keep order of symbols in this Panel
from pandas.compat import OrderedDict
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
def test_oanda_historical_currencypair(self): | ||
start = "2014-03-19T09:00:00Z" | ||
end = "2014-03-21T9:00:00Z" | ||
symbols = ["EUR_USD"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it works with simply
symbols = "EUR_USD"
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: string is now also accepted to represent a single currency pair.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
Great job on this PR!
pn = OANDARestHistoricalInstrumentReader( | ||
symbols=symbols, | ||
start=start, end=end, | ||
freq="S5", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
freq="5T",
which will be convert to Pandas DateOffset
which will be convert to "5T" (using freqstr)
which will be find in dictionnary and return "S5"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
def test_oanda_historical_currencypair2(self): | ||
start = "2014-03-19T09:00:00Z" | ||
end = "2014-03-21T09:00:00Z" | ||
symbols = ["EUR_USD"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A test with several symbols to ensure that we get a Panel will be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OANDA_API_TOKEN is now set on Travis.
I'm not sure but we probably need to export this environment variable in .travis.yml
- export OANDA_API_TOKEN=$OANDA_API_TOKEN
8H -> 8 hour candlesticks, day alignment | ||
12H -> 12 hour candlesticks, day alignment | ||
1D -> 1 day candlesticks, day alignment | ||
1W -> 1 week candlesticks, aligned to start of week |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1W -> W
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
6H -> 6 hour candlesticks, day alignment | ||
8H -> 8 hour candlesticks, day alignment | ||
12H -> 12 hour candlesticks, day alignment | ||
1D -> 1 day candlesticks, day alignment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1D -> D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
12H -> 12 hour candlesticks, day alignment | ||
1D -> 1 day candlesticks, day alignment | ||
1W -> 1 week candlesticks, aligned to start of week | ||
1M -> 1 month candlesticks, aligned to first day of the month |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1M -> M
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
def test_oanda_historical_currencypair(self): | ||
start = "2014-03-19T09:00:00Z" | ||
end = "2014-03-21T9:00:00Z" | ||
symbols = ["EUR_USD"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
Great job on this PR!
@@ -0,0 +1,313 @@ | |||
import pandas as pd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import os
is required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
The initial API TOKEN I committed was a mistake and it has now been revoked. So I will not provide an API TOKEN. Alternatively, someone (else) can provide an OANDA API TOKEN, encrypt it and then store it as encrypted env variable (supported by Travis). |
@femtotrader PR done. |
I added my API key in Travis. But I think you need to add
in |
…ader API) waits indefinitly due threads not gracefully exiting in case of unhandled exception
@femtotrader Is OANDA_API_TOKEN now unset in Travis (or revoked) ? CI fails with 401 errors when running the tests for this reader. |
In [1]: import pandas_datareader.data as web | ||
In [2]: start, end = "2016-01-01", "2016-02-01" | ||
In [3]: symbols = ["EUR_USD"] | ||
In [4]: credentials["accountType"]="practise" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
practice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
In [1]: import pandas_datareader.data as web | ||
In [2]: start, end = "2016-01-01", "2016-02-01" | ||
In [3]: symbols = ["EUR_USD"] | ||
In [4]: credentials["accountType"]="practise" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
empty credentials
dict is required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
In [5]: credentials["apiToken"]="Your OANDA API token" | ||
In [6]: pn = web.DataReader( | ||
symbols, data_source="oanda_historical_currency", | ||
start=start, end=end, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might be able to pass the freq
parameter
Hi, could you please comment on the proposed solution to pandas-dev/pandas#14826 ? (See this comment and below.) |
@femtotrader PR ready. |
I'd be very pleased to merge it... but CI is still failing. I don't understand why. Could you try to add simply
after
You should show my API key (so this approach is not very secure... but enough here because I don't mind if you display it in a Travis build.) |
@femtotrader The API token is missing. See output. Once added back, I will revert the last commit. |
That's very odd because I set in Travis
Put this token in code because I'm fed up with Travis |
About request and multithreading you might have a look at |
I advise you not to. OANDA may revoke this token at best or close your account for some breach (after all those are considered personal info) I personally set the OANDA_API_TOKEN env variable in my bash scripts so my environment is quite close to Travis I suppose. Are you using encrypted environment value or not ?
It seems like encrypted env value are not provided to
Python is not well suited for multithreaded code due to GIL. I have limited GIL knownlegde and initially thought this was a concern for CPU intensive apps. Turns out it also has implications with IO as per the doc provided. This PR uses |
@davidandreoletti |
a)Do you know why the environment variable b) if @hootnot's solution cannot be used, I don't have access to Travis to investigate the environment variable issue, can you investigate it or grant me temporary access to Travis ? c) I added another reader OANDARestAccountInstrumentReader. It is returning hard coded data and is missing in the documentation because I could not get it to fetch real data with my personal account. What do you want to do with it ("keep it undocumented in the documentation + warning in the source code", "something else") ? d) I think we should move the oandrest.py classes (+ perhaps oanda.py) into a proper python module. What do you think ? |
Pinging @sinhrks @davidastephens @jreback |
@davidandreoletti @femtotrader regarding a): This is not how it works. Travis can never know the value of the OANDA_API_TOKEN this way. If you use it from your shell this works. But the shell Travis uses has the value not set, so it it empty. Travis gets its environment variables from .travis.yml. Structure described in: https://docs.travis-ci.com/user/environment-variables/ If you want to use the/your token in the tests (and I would advise you not to) you need to set it up the way Travis points out in their docs, using encryption. The owner of the repo must do this since it involves a procedure with keys.
But when you do it this way you share a token as a contributor to the repo-owner. You do not want that for both parties:
If you do want to use the token in the tests this way I suggest the best solution is that the repo-owner obtains a token and does the Travis setup with that token. This way the owner has control and responsibility for the token and contributors can still run the tests. So if you want to use encrypted environment variables, it is up to to repo-owner to set this up. I use this encryption setup in https://github.com/hootnot/oanda-api-v20/ to deploy to pypi automatically when tagging a release. I still think that you should use mocking to setup your tests. With mocking you can bypass the issue of the token. In the next example you find a Panda class that represents the same kind of flow as you use in OANDARestHistoricalInstrumentReader : import sys
import json
import unittest
import oandapyV20
import oandapyV20.endpoints.instruments as instruments
import requests_mock
data = range(10) # just some testdata,
environment = "practice"
access_token = None
api = None
class Panda(object):
# some test class supposing to read data from OANDA using oandapyV20
# ( and it will if you pass it a valid token )
# the access_token is not relevant since the request is mocked
def __init__(self, access_token="xxx"):
self.client = oandapyV20.API(access_token=access_token)
def get_data(self, instrument, params):
r = instruments.InstrumentsCandles(instrument=instrument, params=params)
# this will call the mocked URI and return the data you provided setting up the mock request
rv = self.client.request(r)
return rv
class OA(unittest.TestCase):
def setUp(self):
global access_token
global api
# override the environments values with the ones relevant for the mock requests
setattr(sys.modules["oandapyV20.oandapyV20"],
"TRADING_ENVIRONMENTS",
{"practice": {
"stream": "https://test.com",
"api": "https://test.com",
}})
# setup the client the regular way
api = oandapyV20.API(environment=environment,
access_token=access_token)
api.api_url = "https://test.com"
@requests_mock.Mocker()
def test_candles(self, mock_req):
params = { "granularity": "M5", "count": 5}
r = instruments.InstrumentsCandles(instrument="EUR_USD", params=params)
mock_req.register_uri('GET', "{}/{}".format(api.api_url, r),
text=json.dumps(data))
# the statements before registered the uri, when this one gets called with this parameters, it returns
# the associated data
# try that with this test class
panda = Panda()
pandadata = panda.get_data("EUR_USD", params=params)
self.assertEqual(pandadata, data)
if __name__ == "__main__":
unittest.main() create some testdata representing the data you get in real from a request. Store this in a file or files along with your tests. Write some method to read this data to setup the mock request and you can simulate the IO with OANDA's REST V20. Regarding d) I think it is wise to split some things up. The oandarest.py contains a lot of constants:
These could be separated and that makes them importable in other code also, which is currently not the case. It also reduces the size of oandarest.py about 10% making it more readable. |
@davidandreoletti Support for Oanda was removed with #366 Consider closing this issue |
@davidandreoletti you did a great work on this PR but |
No description provided.